class Array
  def sum
    inject( nil ) { |sum,x| 
    if sum
      if x
        sum + x
      else 
        nil
      end
    else
      if x
        x
      else
        nil
      end
    end
      }
  end
end

class Game
  
  def initialize
    # number of pins knocked over by each ball
    @balls = []

    # position in @balls of first ball of each frame
    @frame_indexes = [0] 

  end

  def score
    @frame_indexes.collect { |i| score_for_frame(i) }.sum
  end

  def ball(n)
    if first_ball?
      if n == 10 	# stee-rike! so next ball is new frame'
        @frame_indexes<<@balls.length + 1
      end
		else 					# second ball of frame so next ball is new frame'
			raise "impossible roll" if (@balls.last + n) > 10
      @frame_indexes<<@balls.length + 1
    end
    @balls << n
  end

	def first_ball?
		@frame_indexes.last == @balls.length
	end

  def frame()
    @frame_indexes.length
  end 

  def score_a_frame(i)
    if i==nil
      return nil
    end
    if i + 1>=@balls.length
      # frame is incomplete so we can't score it yet
      return nil
    end
    if @balls[i] == 10
      #stee-rike!
      ball_count = 4
    elsif @balls[i..i + 1].sum == 10
      ball_count = 3
    else
      ball_count = 2
    end
    if i + ball_count - 1 > @balls.length
      # need more rolls to calculate score
      nil
    else
      @balls[i..i + ball_count].sum
    end
  end

  def score_for_frame(n)
    if n==nil:
        nil
    else
      score_a_frame(@frame_indexes[n])
    end
  end

end

if __FILE__==$0
  require 'test/unit'

  class GameTest < Test::Unit::TestCase
    
    def setup
      @game = Game.new
    end
    
    def test_score_no_balls
      # this is an incomplete frame, so should be nil
      assert_equal nil, @game.score
    end
    
    def test_score_one_ball
      # after one ball, the score should be nil
      # since the frame is incomplete
      @game.ball(5)
      assert_equal nil, @game.score_for_frame(0)
      assert_equal nil, @game.score
    end

    def test_score_one_frame
      # after two balls, the frame count should be incremented
      # and score should be sum of two balls, if not strike or spare
      assert_equal 1, @game.frame
      @game.ball(5)
      @game.ball(3)
      assert_equal 2, @game.frame
      assert_equal nil, @game.score
    end

    def test_score_one_strike
      # after a strike, the frame count should be incremented
      # and score should be nil, since frame is incomplete
      @game.ball(10)
      assert_equal 2, @game.frame
      assert_equal nil, @game.score
    end
    
    def test_score_for_frame
      # the score for the the first frame should be the sum
      # of the first two throws
      @game.ball(4)
      @game.ball(3)
      assert_equal(7, @game.score_for_frame(0))
      assert_equal(nil, @game.score)
      @game.ball(10)
      
      # score should be nil (unknown) because of the strike 
      assert_equal(nil, @game.score_for_frame(1))
      assert_equal(nil, @game.score)
      @game.ball(5)
      @game.ball(3)
      # last frame should be updated due to strike
      assert_equal(18, @game.score_for_frame(1))
      assert_equal(8,@game.score_for_frame(2))
      assert_equal(nil, @game.score)
    end

    def test_ten_pin_limit
      # rolling more than 10 in a frame should raise an exception
      @game.ball 9
      assert_raise RuntimeError do
        @game.ball 2
      end
    end

		def test_game_complete
			# we should be able to determine if the game is complete?
			assert !@game.complete?
			12.times { @game.ball(10) }
			assert @game.complete?
		end
  end
end
